
import unittest

class TestDict(unittest.TestCase):
    def test_construct(self):
        expected_dict = {1:"cheka",2:"stasi"}
        
        # ---------- construct from sequence
        dict_from_tuple = dict(([1,"cheka"],[2,"stasi"])) 
        self.assertEqual(expected_dict,dict_from_tuple)
        
        dict_from_list = dict([[1,"cheka"],[2,"stasi"]]) 
        self.assertEqual(expected_dict,dict_from_list)
        
        # ---------- construct from default values 
        dict_from_defvalues = {}.fromkeys((1,2),"")
        self.assertEqual({1:"",2:""},dict_from_defvalues)
        
        dict_fromkeys = dict.fromkeys((1,2))
        self.assertEqual({1:None,2:None},dict_fromkeys)
        
        # ---------- construct from zip
        self.assertEqual( expected_dict,dict(zip((1,2),("cheka","stasi"))) )
        
    def test_exception_nokey(self):
        self.assertRaises(KeyError,lambda : {}["nonexisted"])
        
    def test_equal_same(self):
        dict1 = {1:"cheka","stasi":2}
        dict2 = {1:"cheka","stasi":2} # mutable type always create a totally new instance
        
        self.assertTrue(dict1 == dict2)
        self.assertTrue(dict1 is not dict2)
        
        # -------------- order when being initialized will not effect equality
        # -------------- because dictionary will not keep order
        dict_diff_order = {"stasi":2,1:"cheka"}
        self.assertEqual(dict1,dict_diff_order)
        
    def test_in(self):
        """
        has_key is obsolete, so it is recommended to check whether a key exists in the dictionary by using "in" or "not in"
        """
        adict = {1:"cheka","stasi":100.0}
        self.assertTrue(1 in adict)
        self.assertTrue("stasi" in adict)
        self.assertTrue("nokey" not in adict)
        
    def test_copy(self):
        oridict = {1:[]}
        cpydict = oridict.copy()
        
        self.assertEqual(oridict,cpydict)
        self.assertTrue(oridict is not cpydict)
        self.assertTrue(oridict[1] is cpydict[1]) # just a shallow copy
        
    def test_remove(self):
        # ---------------------- remove by "del"
        adict = {1:"cheka","stasi":2,"mss":100.0}
        del adict["mss"]
        self.assertEqual({1:"cheka","stasi":2},adict)
        
        # ---------------------- remove by "pop"
        self.assertEqual("cheka",adict.pop(1))
        self.assertEqual({"stasi":2},adict)
        
        # raise exception when key not existed
        self.assertRaises(KeyError,lambda : adict.pop("nonexisted"))
        
        # return default value when key not existed and default value is set
        self.assertEqual("default",adict.pop("nonexisted","default"))
        
        # ---------------------- remove by "clear"
        adict.clear()
        self.assertEqual(0,len(adict))
    
    def test_get(self):
        adict = {1:"cheka"}
        self.assertEqual("cheka",adict[1])
        
        self.assertRaises(KeyError,lambda : adict[0])
        
        self.assertEqual("default",adict.get(2,"default"))
        self.assertEqual("cheka",adict.get(1,"default"))
        
        # ----------- if default value is not given, just return None, other than throw exception
        self.assertTrue(adict.get("nokey") is None)
        
    def test_setdefault(self):
        """
        I don't think "setdefault" is very useful
        it can possibly hide the error that inserts a miss-typed key
        and it is not very useful for us to implement the .NET LINQ ILookup in python
        because multiple key cannot share the same [], and if we always provide a new [] each time
        we call "setdefault", then a lot of [] will be wasted
        """
        
    def test_update(self):
        """
        unlike in C#, where add with existed key will throw exception
        but in Python, no builtin way to notify you the key has existed
        if you provide the same key, you are upating old values with new values
        you have to use "in" to check whehter the key has existed or not yourself
        """
        adict = {}
        
        key = 1
        ori_value = "cheka"
        
        # -------------------- first write by key is adding
        adict[key] = ori_value
        self.assertEqual(1,len(adict))
        self.assertEqual(ori_value,adict[key])
        
        # -------------------- following write by key is updating
        new_value = "stasi"
        adict[key] = new_value
        self.assertEqual(1,len(adict))
        self.assertEqual(new_value,adict[key])
        
    def test_concatenate(self):
        adict = {1:"cheka"}
        adict.update({"stasi":2})
        self.assertEqual({1:"cheka","stasi":2},adict)
        
        # --------------- old values will be overriden by new values with duplicated key
        adict.update({"stasi":"newvalue"})
        self.assertEqual({1:"cheka","stasi":"newvalue"},adict)
        
    def test_hybrid_types(self):
        """
        neither the key or the value must be of the same type
        """
        hybrid_dict = {1:"cheka","stasi":100.0}
        self.assertEqual("cheka",hybrid_dict[1])
        self.assertAlmostEqual(100.0,hybrid_dict["stasi"])
        
    def test_iter_return_list(self):
        """
        keys, values, items return list, may use a lot of memory when there are a lot of items in the dictionary
        maybe not very suitable for case that deal with only item each time
        """
        adict = {1:"stasi",2:"cheka"}
        
        # --------------------- actually, we cannot guarantee the order of returned list
        self.assertEqual([1,2],adict.keys())
        self.assertEqual(["stasi","cheka"],adict.values())
        self.assertEqual([(1,"stasi"),(2,"cheka")],adict.items())
        
        # --------------------- dictionary is a iterator which returns key
        iterated_keys = [x for x in adict]
        self.assertEqual(adict.keys(),iterated_keys)
        
    def test_iter_return_iterator(self):
        """
        like LINQ in .NET, implement a Defered Execution feature
        return iterator that will not copy values but consume only one item each time
        """
        adict = {1:"stasi","cheka":2}
        
        self.assertEqual([1,"cheka"],[k for k in adict.iterkeys()])
        self.assertEqual(["stasi",2],[v for v in adict.itervalues()])
        self.assertEqual([(1,"stasi"),("cheka",2)],[item for item in adict.iteritems()])

    def test_simulate_swtich_case(self):
        choice = {
            "choice1":"action1",
            "choice2":"action2"
        }
        default_action = "default"
        self.assertEqual("action1",choice.get("choice1",default_action))
        self.assertEqual("default",choice.get("unrecognized",default_action))

if __name__ == "__main__":
    unittest.main()